{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cb0bbeb2-f69d-4e91-82ca-d8511f79028b",
   "metadata": {},
   "source": [
    "# 摘要：生成（可能很长的）文本的摘要。\n",
    "\n",
    "### 文本摘要总结\n",
    "\n",
    "假设你有一组文档（如 PDF 文件、Notion 页面、客户问题等），并且你希望对这些内容进行摘要。\n",
    "\n",
    "鉴于大语言模型（LLMs）在理解和合成文本方面的强大能力，它们非常适合用于这项任务。\n",
    "\n",
    "在检索增强生成（Retrieval-Augmented Generation）的背景下，文本摘要可以帮助提炼大量检索到的文档中的信息，为 LLM 提供更清晰的上下文。\n",
    "\n",
    "在本次讲解中，我们将介绍如何使用 LLM 对来自多个文档的内容进行摘要。\n",
    "\n",
    "![示例图片](../../assets/imgs/summarization_use_case_1.png)  \n",
    "\n",
    "### 涵盖的概念\n",
    "\n",
    "我们将介绍以下概念：\n",
    "\n",
    "- 使用语言模型（Language Models）。\n",
    "- 使用文档加载器（Document Loaders），特别是使用 `WebBaseLoader` 从 HTML 网页中加载内容。\n",
    "- 两种用于摘要或合并文档的方法：\n",
    "  - **Stuff 方法**：简单地将所有文档拼接成一个提示词（prompt）；\n",
    "  - **Map-Reduce 方法**：适用于较大文档集的情况。它会将文档分成批次处理，先对每个批次进行摘要，再对这些摘要结果进行最终汇总。\n",
    "\n",
    "### 概述\n",
    "\n",
    "构建文本摘要器时，一个核心问题是：如何将你的文档输入到大语言模型（LLM）的上下文窗口中。常见的两种方法是：\n",
    "\n",
    "- **Stuff 方法**：简单地将所有文档“塞入”一个提示词（prompt）中。\n",
    "\n",
    "- **Map-Reduce 方法**：在“映射（map）”阶段分别对每个文档进行摘要，然后在“归约（reduce）”阶段将这些摘要合并为最终的摘要。\n",
    "\n",
    "请注意，**Map-Reduce 方法**在子文档的理解不依赖于先前上下文的情况下特别有效，例如当你要摘要大量较短的文档时。而在其他情况下，比如摘要一本小说或具有内在顺序结构的长文本时，**迭代精炼（iterative refinement）** 方法可能更有效。\n",
    "\n",
    "![示例图片](../../assets/imgs/summarization_use_case_2.png)  \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2720582b-4bcb-4a77-a846-7c4141fdc59a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "loader = WebBaseLoader(\"https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202506/t20250620_6962720.html\")\n",
    "docs = loader.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "18d72ee4-73a1-41bd-9e03-378715f13deb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "try:\n",
    "    # load environment variables from .env file (requires `python-dotenv`)\n",
    "    from dotenv import load_dotenv\n",
    "\n",
    "    _ = load_dotenv()\n",
    "except ImportError:\n",
    "    pass\n",
    "\n",
    "if not os.environ.get(\"DASHSCOPE_API_KEY\"):\n",
    "  os.environ[\"DASHSCOPE_API_KEY\"] = getpass.getpass(\"Enter API key for OpenAI: \")\n",
    "\n",
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "\n",
    "llm  = ChatTongyi(\n",
    "    streaming=True,\n",
    "    name=\"qwen-turbo\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c21f8e34-63cf-4bf5-950b-bd41bb5c8bc6",
   "metadata": {},
   "source": [
    "## Stuff 方法：通过一次 LLM 调用进行摘要\n",
    "\n",
    "我们可以使用 `create_stuff_documents_chain`，特别是在使用具有较大上下文窗口的模型时，例如：\n",
    "\n",
    "- 128k 令牌的 OpenAI gpt-4o  \n",
    "- 200k 令牌的 Anthropic claude-3-5-sonnet-20240620  \n",
    "\n",
    "该链会接收一个文档列表，将所有文档插入到一个提示词（prompt）中，然后将这个提示词传递给大语言模型（LLM）进行处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6947da63-43c2-43b1-b4cc-42291f3770be",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "天津市工业和信息化局组织了一场媒体活动，中外记者参观了滨海新区现代产业展示交流中心，深入了解了滨海新区五大国家级开发区的产业发展情况。在企业展示区，航天泰心科技有限公司的“火箭心”和天地伟业技术有限公司的安防系统受到广泛关注。活动中，记者们对天津企业的高科技产品表现出浓厚兴趣，甚至有外国记者表示想购买这些产品。此外，记者们还参观了海油工程的智能制造基地，体验了数字化展厅的互动环节，并观摩了职业技能比武。巴基斯坦媒体代表表示，中国的发展经验对发展中国家具有重要借鉴意义，希望通过报道促进技术交流和能力建设。\n"
     ]
    }
   ],
   "source": [
    "from langchain.chains.combine_documents import create_stuff_documents_chain\n",
    "from langchain.chains.llm import LLMChain\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "# Define prompt\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", \"Write a concise summary of the following:\\\\n\\\\n{context}\")]\n",
    ")\n",
    "\n",
    "# Instantiate chain\n",
    "chain = create_stuff_documents_chain(llm, prompt)\n",
    "\n",
    "# Invoke chain\n",
    "result = chain.invoke({\"context\": docs})\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a3d2f3e-147b-4a9c-9292-f9dcdcd4e087",
   "metadata": {},
   "source": [
    "### 流式传输  \n",
    "请注意，我们还可以逐个令牌（token）地流式传输结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d7ead6d3-05b0-4e01-bf9a-999a21514622",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "天津市|工业|和|信息化局主办的|活动中，中外记者|参观了滨海新区|现代产业展示交流|中心，深入了解了|滨海新区五大国家级|开发区的产业布局|与发展成果。企业|展示区中，|航天泰心科技|的“火箭心|”和天地伟|业的安防系统|吸引了外国记者的高度|关注，他们对|天津企业的高科技产品|表现出极大兴趣。|此外，在海油|工程的智能制造基地|，外国记者体验|了数字化展厅的|互动展示，并观|看了职业技能比武|，对“滨|城智造”的实力|给予高度评价。|巴基斯坦媒体代表表示|，希望通过报道中国|的发展经验，为|巴基斯坦带来实际帮助|，推动两国合作|进一步深化。||"
     ]
    }
   ],
   "source": [
    "for token in chain.stream({\"context\": docs}):\n",
    "    print(token, end=\"|\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46f48d0f-bd7a-4036-b1c4-7f02ced14591",
   "metadata": {},
   "source": [
    "### Map-Reduce：通过并行化摘要长文本\n",
    "\n",
    "让我们来详细解析一下 Map-Reduce 的方法。在此方法中，我们将首先使用一个大语言模型（LLM）将每个文档映射为一个单独的摘要（map 阶段），然后将这些摘要合并或整合成一个全局的最终摘要（reduce 阶段）。\n",
    "\n",
    "请注意，**map 阶段通常会对输入文档进行并行处理**。\n",
    "\n",
    "LangGraph 基于 langchain-core 构建，支持 Map-Reduce 工作流，非常适合解决此类问题：\n",
    "\n",
    "- LangGraph 允许对各个步骤（例如连续的摘要操作）进行流式传输，从而实现对执行过程的更精细控制；\n",
    "- LangGraph 的检查点机制支持错误恢复，并可扩展为人机协同循环（human-in-the-loop）的工作流程，也更容易集成到对话型应用中；\n",
    "- 如下所示，LangGraph 的实现方式非常易于修改和扩展。\n",
    "\n",
    "---\n",
    "\n",
    "### **Map 阶段**\n",
    "\n",
    "首先，我们定义与 map 步骤相关联的提示词（prompt）。我们可以使用与前面 \"Stuff\" 方法中相同的摘要提示词："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "34dc10da-ab31-4bdc-8e03-2aeff2ffa34f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "# 写下以下内容的简明摘要\n",
    "map_prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", \"写下以下内容的简明摘要:\\\\n\\\\n{context}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f937f4b-ab1e-498f-8863-f5462849779f",
   "metadata": {},
   "source": [
    "### Reduce 阶段\n",
    "\n",
    "我们还会定义一个提示词，它接收文档映射（mapping）的结果，并将其合并（reduce）为一个最终的输出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "8d9b8818-8cd5-4454-a31d-b2c43625dc13",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将这些提炼成最终的综合摘要的主题。\n",
    "reduce_template = \"\"\"\n",
    "以下是一组摘要内容：\n",
    "\n",
    "{docs}\n",
    "\n",
    "请基于这些内容提炼出一个最终的、整合后的摘要，概括其主要主题。\n",
    "\"\"\"\n",
    "\n",
    "reduce_prompt = ChatPromptTemplate([(\"human\", reduce_template)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16cd623d-acdb-43bf-9960-51d4b6c575cd",
   "metadata": {},
   "source": [
    "## 通过 LangGraph 进行编排\n",
    "\n",
    "下面我们将实现一个简单的应用：它首先对文档列表中的每个文档执行摘要（map 步骤），然后使用上面定义的提示词将这些摘要合并为一个最终结果（reduce 步骤）。\n",
    "\n",
    "当文本长度相对于大语言模型（LLM）的上下文窗口来说过长时，Map-Reduce 流程尤其有用。对于长文本，我们需要一种机制来确保在 reduce 阶段要处理的上下文不会超出模型的上下文长度限制。在这里，我们实现了一种递归式的“折叠”摘要方法：根据 token 数量限制将输入分成多个块，并对每个块生成摘要。重复这个过程，直到所有摘要的总长度在目标限制范围内。这种方法可以用于对任意长度的文本进行摘要。\n",
    "\n",
    "---\n",
    "\n",
    "### 第一步：将博客文章拆分为更小的“子文档”以进行映射处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "821da891-ad09-460c-9831-c5f9975f564a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Created a chunk of size 5920, which is longer than the specified 1000\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generated 3 documents.\n"
     ]
    }
   ],
   "source": [
    "from langchain_text_splitters import CharacterTextSplitter\n",
    "\n",
    "text_splitter = CharacterTextSplitter.from_tiktoken_encoder(\n",
    "    chunk_size=1000, chunk_overlap=0\n",
    ")\n",
    "split_docs = text_splitter.split_documents(docs)\n",
    "print(f\"Generated {len(split_docs)} documents.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed2790c6-990d-4f26-949e-e86c41d1ae09",
   "metadata": {},
   "source": [
    "接下来，我们定义我们的图（graph）。请注意，我们人为设定了一个较低的最大 token 长度限制（1,000 个 token），以演示“折叠”（collapsing）步骤。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "1df12b3e-b04d-4930-b251-289be484fb0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import operator\n",
    "from typing import Annotated, List, Literal, TypedDict\n",
    "\n",
    "from langchain.chains.combine_documents.reduce import (\n",
    "    acollapse_docs,\n",
    "    split_list_of_docs,\n",
    ")\n",
    "from langchain_core.documents import Document\n",
    "from langgraph.constants import Send\n",
    "from langgraph.graph import END, START, StateGraph\n",
    "from langchain_core.messages import BaseMessage \n",
    "import tiktoken\n",
    "\n",
    "token_max = 1000\n",
    "\n",
    "# 使用 tiktoken 替代原有的 token 计数方法\n",
    "def tiktoken_token_counter(messages: List[BaseMessage]) -> int:\n",
    "    \"\"\"使用 tiktoken 本地计算 token 数量（避免网络请求）\"\"\"\n",
    "    enc = tiktoken.get_encoding(\"cl100k_base\")  \n",
    "    text = \" \".join([msg.content for msg in messages])\n",
    "    return len(enc.encode(text))\n",
    "\n",
    "# 修改 length_function 使用 tiktoken\n",
    "def length_function(documents: List[Document]) -> int:\n",
    "    \"\"\"使用本地 tiktoken 计算 token 数量（避免网络请求）\"\"\"\n",
    "    # 将 Document 对象转换为类似 BaseMessage 的结构\n",
    "    messages = [BaseMessage(content=doc.page_content, type=\"human\") for doc in documents]\n",
    "    return tiktoken_token_counter(messages)\n",
    "\n",
    "# This will be the overall state of the main graph.\n",
    "# It will contain the input document contents, corresponding\n",
    "# summaries, and a final summary.\n",
    "class OverallState(TypedDict):\n",
    "    # Notice here we use the operator.add\n",
    "    # This is because we want combine all the summaries we generate\n",
    "    # from individual nodes back into one list - this is essentially\n",
    "    # the \"reduce\" part\n",
    "    contents: List[str]\n",
    "    summaries: Annotated[list, operator.add]\n",
    "    collapsed_summaries: List[Document]\n",
    "    final_summary: str\n",
    "\n",
    "\n",
    "# This will be the state of the node that we will \"map\" all\n",
    "# documents to in order to generate summaries\n",
    "class SummaryState(TypedDict):\n",
    "    content: str\n",
    "\n",
    "\n",
    "# Here we generate a summary, given a document\n",
    "async def generate_summary(state: SummaryState):\n",
    "    prompt = map_prompt.invoke(state[\"content\"])\n",
    "    response = await llm.ainvoke(prompt)\n",
    "    return {\"summaries\": [response.content]}\n",
    "\n",
    "\n",
    "# Here we define the logic to map out over the documents\n",
    "# We will use this an edge in the graph\n",
    "def map_summaries(state: OverallState):\n",
    "    # We will return a list of `Send` objects\n",
    "    # Each `Send` object consists of the name of a node in the graph\n",
    "    # as well as the state to send to that node\n",
    "    return [\n",
    "        Send(\"generate_summary\", {\"content\": content}) for content in state[\"contents\"]\n",
    "    ]\n",
    "\n",
    "# 收集摘要\n",
    "def collect_summaries(state: OverallState):\n",
    "    return {\n",
    "        \"collapsed_summaries\": [Document(summary) for summary in state[\"summaries\"]]\n",
    "    }\n",
    "\n",
    "\n",
    "async def _reduce(input: dict) -> str:\n",
    "    prompt = reduce_prompt.invoke(input)\n",
    "    response = await llm.ainvoke(prompt)\n",
    "    return response.content\n",
    "\n",
    "\n",
    "# Add node to collapse summaries \n",
    "# 合并摘要\n",
    "async def collapse_summaries(state: OverallState):\n",
    "    doc_lists = split_list_of_docs(\n",
    "        state[\"collapsed_summaries\"], length_function, token_max\n",
    "    )\n",
    "    results = []\n",
    "    for doc_list in doc_lists:\n",
    "        results.append(await acollapse_docs(doc_list, _reduce))\n",
    "\n",
    "    return {\"collapsed_summaries\": results}\n",
    "\n",
    "\n",
    "# This represents a conditional edge in the graph that determines\n",
    "# if we should collapse the summaries or not\n",
    "def should_collapse(\n",
    "    state: OverallState,\n",
    ") -> Literal[\"collapse_summaries\", \"generate_final_summary\"]:\n",
    "    num_tokens = length_function(state[\"collapsed_summaries\"])\n",
    "    if num_tokens > token_max:\n",
    "        return \"collapse_summaries\"\n",
    "    else:\n",
    "        return \"generate_final_summary\"\n",
    "\n",
    "\n",
    "# Here we will generate the final summary\n",
    "async def generate_final_summary(state: OverallState):\n",
    "    response = await _reduce(state[\"collapsed_summaries\"])\n",
    "    return {\"final_summary\": response}\n",
    "\n",
    "\n",
    "# Construct the graph\n",
    "# Nodes:\n",
    "graph = StateGraph(OverallState)\n",
    "graph.add_node(\"generate_summary\", generate_summary)  # same as before\n",
    "graph.add_node(\"collect_summaries\", collect_summaries)\n",
    "graph.add_node(\"collapse_summaries\", collapse_summaries)\n",
    "graph.add_node(\"generate_final_summary\", generate_final_summary)\n",
    "\n",
    "# Edges:\n",
    "graph.add_conditional_edges(START, map_summaries, [\"generate_summary\"])\n",
    "graph.add_edge(\"generate_summary\", \"collect_summaries\")\n",
    "graph.add_conditional_edges(\"collect_summaries\", should_collapse)\n",
    "graph.add_conditional_edges(\"collapse_summaries\", should_collapse)\n",
    "graph.add_edge(\"generate_final_summary\", END)\n",
    "\n",
    "app = graph.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "bb50c4f8-be2f-4f13-9986-b1bc389199f5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import Image\n",
    "\n",
    "Image(app.get_graph().draw_mermaid_png())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6717029-e555-45b4-be8b-a13de39c5e6c",
   "metadata": {},
   "source": [
    "在运行该应用程序时，我们可以对图（graph）的执行过程进行流式输出，以观察其执行步骤序列。下面示例中，我们仅打印出每个步骤的名称。\n",
    "\n",
    "请注意，由于图中存在循环结构，因此在执行时指定一个 `recursion_limit`（递归限制）会很有帮助。当超过该限制时，系统将抛出一个特定的错误，以防止无限循环。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "6223d506-1951-4009-b8c1-16ea714a26e3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['generate_summary']\n",
      "['generate_summary']\n",
      "['generate_summary']\n",
      "['collect_summaries']\n",
      "['generate_final_summary']\n"
     ]
    }
   ],
   "source": [
    "async for step in app.astream(\n",
    "    {\"contents\": [doc.page_content for doc in split_docs]},\n",
    "    {\"recursion_limit\": 10},\n",
    "):\n",
    "    print(list(step.keys()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "23d87bbf-dd70-4e02-be6f-3fc67d9819b3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'generate_final_summary': {'final_summary': '天津市通过“智慧天津”建设展现了制造业和产业创新的显著成果，受到国内外关注。《今晚报》报道显示，天津市工业和信息化局在“工信动态”栏目中介绍了相关成就，强调产品实力获得认可。同时，中外记者参观了天津滨海新区现代产业展示交流中心，对企业创新成果如“火箭心”和安防系统表现出浓厚兴趣，并体验了数字化智能制造基地。此外，巴基斯坦媒体人希望借鉴中国经验推动技术交流与能力建设。相关信息还涉及天津市工业和信息化局官方网站的基本信息和服务功能，提示用户使用更高版本浏览器以优化访问体验。'}}\n"
     ]
    }
   ],
   "source": [
    "print(step)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa801025-9ef3-4759-b902-14920c346bda",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
